package com.heo.server;

import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION;
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.net.InetSocketAddress;
import java.util.Date;
import java.util.List;
import org.apache.log4j.Logger;
import com.heo.pub.ServerConfig;
import com.heo.queue.TaskQueue;
import com.heo.util.NettyChannelMapUtils;
import com.heo.util.SystemInfo;
import com.heo.util.TimeHelper;
import com.heo.util.login.LoginSecurity;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.handler.codec.http.multipart.MemoryAttribute;
import io.netty.util.AsciiString;

public class ConsoleServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>
{
  private static Logger log = Logger.getLogger(ConsoleServerHandler.class);
  private static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive");

  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception
  {
    // 判断是否是符合要求的ip地址连接上来。如果是，就建立连接，否则就断开
    String remoteIpAddress = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
    if (ServerConfig.SOURCE_IP != null && ServerConfig.SOURCE_IP.length > 0)
    {
      for (int i = 0; i < ServerConfig.SOURCE_IP.length; i++)
      {
        if (remoteIpAddress.equals(ServerConfig.SOURCE_IP[i].trim()))
        {
          return;
        }
      }
      ctx.channel().close().addListener(new ChannelFutureListener()
      {
        // 通道关闭后，触发本事件
        @Override
        public void operationComplete(ChannelFuture future) throws Exception
        {
          log.error("[server][console][" + remoteIpAddress + "]访问控制台的IP不在许可的IP范围，链接已断开。");
        }
      });
    }
  }

  @Override
  public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception
  {
    String channelId = ctx.channel().id().asShortText();// 连接ID
    String remoteIpAddress = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
    try
    {
      if (HttpUtil.is100ContinueExpected(req))
      {
        ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
      }

      String content = "";

      // 判断连接是否通过校验了
      if (ConsoleChannelMap.get(channelId) == null && !req.uri().equalsIgnoreCase("/login.jsp") && !req.uri().equalsIgnoreCase("/login.do"))
      {
        // 未登录，需要登录
        log.error("[server][console][" + remoteIpAddress + "]登录控制台失败，用户未登录，跳转到登录页。");
        content = "302";
      }
      else
      {
        if (req.uri().equalsIgnoreCase("/login.jsp"))
        {
          // 如果是请求的登录页，则显示登录页的内容
          content = ServerConfig.CONSOLE_LOGIN_TEMPLATE;
        }
        else if (req.uri().equalsIgnoreCase("/login.do"))
        {
          // 判断用户名和密码是否正确
          HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), req);
          List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
          String loginName = null;
          String password = null;
          for (InterfaceHttpData data : postData)
          {
            if (data.getHttpDataType() == HttpDataType.Attribute)
            {
              MemoryAttribute attribute = (MemoryAttribute) data;
              if ("loginName".equals(attribute.getName()))
              {
                loginName = attribute.getValue();
              }
              if ("password".equals(attribute.getName()))
              {
                password = attribute.getValue();
              }
            }
          }
          if (ServerConfig.CONSOLE_ADMIN.equals(loginName) && ServerConfig.CONSOLE_PASSWORD.equals(password))
          {
            // 密码验证通过
            // 当登录成功时，校验一下内存里的信息，是否触发暴力破解的临界点，如果是，就返回登录错误
            if (!LoginSecurity.getInstance().checkLogin(loginName + "," + remoteIpAddress))
            {
              // 如果内存中发现有暴力破解的情况，则即使密码是对的，也要返回错误
              log.error("[server][console][" + remoteIpAddress + "]登录控制台失败，用户名和密码正确，但尝试次数超过许可的限制。loginName:" + loginName + ",password:" + password);
              content = "fail";
            }
            else
            {
              ConsoleChannelMap.add(channelId, (SocketChannel) ctx.channel());
              log.info("[server][console][" + remoteIpAddress + "]登录控制台成功。");
              content = "success";
            }
          }
          else
          {
            LoginSecurity.getInstance().addErrLogin(loginName + "," + remoteIpAddress);// 放暴力破解模块，记录登录错误
            log.error("[server][console][" + remoteIpAddress + "]登录控制台失败，用户名或密码错误。loginName:" + loginName + ",password:" + password);
            content = "fail";
          }
        }
        // 登录成功
        else if (req.uri().equalsIgnoreCase("/") || req.uri().equalsIgnoreCase("/index.jsp"))
        {
          content = ServerConfig.CONSOLE_INDEX_TEMPLATE;
          // 收集服务器运行状态
          content = content.replace("{START_TIME}", TimeHelper.dateTimeToStr(ServerConfig.START_TIME));
          content = content.replace("{SERVER_TIME}", TimeHelper.dateTimeToStr(new Date()));
          content = content.replace("{CURR_CONNECTIONS}", String.valueOf(NettyChannelMapUtils.getSocketSize()));
          content = content.replace("{CURR_DEVICES}", String.valueOf(NettyChannelMapUtils.getSocketSize()));
          content = content.replace("{PORT}", String.valueOf(ServerConfig.PORT));
          content = content.replace("{READ_IDLE_TIME}", String.valueOf(ServerConfig.READ_IDLE_TIME));
          content = content.replace("{WRITE_IDLE_TIME}", String.valueOf(ServerConfig.WRITE_IDLE_TIME));
          content = content.replace("{ALL_IDLE_TIME}", String.valueOf(ServerConfig.ALL_IDLE_TIME));
          content = content.replace("{CONNECT_TIME_OUT}", String.valueOf(ServerConfig.CONNECT_TIME_OUT));
          content = content.replace("{MAX_READABLE_BYTES_LENGTH}", String.valueOf(ServerConfig.MAX_READABLE_BYTES_LENGTH));
          content = content.replace("{SOURCE_IP}", ServerConfig.getSourceIp());
          
          content = content.replace("{CONTROL_URL}", ServerConfig.CONTROL_URL);

          content = content.replace("{MAX_MEMORY}", SystemInfo.getMaxMemory());
          content = content.replace("{FREE_MEMORY}", SystemInfo.getFreeMemory());
          content = content.replace("{USE_MEMORY}", SystemInfo.getUseMemory());

          content = content.replace("{THREAD_COUNT}", String.valueOf(SystemInfo.getThreadCount()));
          
          content = content.replace("{LAST_SEND_TIME}", ServerConfig.LAST_SEND_TIME);
          content = content.replace("{SEND_QUEUE_SIZE}", String.valueOf(TaskQueue.getSendTaskSize()));
          content = content.replace("{RECEIVE_QUEUE_SIZE}", String.valueOf(TaskQueue.getReceiveTaskSize()));
        }
        else if (req.uri().equalsIgnoreCase("/get_info.jsp"))
        {
          content = String.valueOf(NettyChannelMapUtils.getSocketSize()) + "|";// 当前连接数
          content += String.valueOf(NettyChannelMapUtils.getSocketSize()) + "|";// 当前设备数
          content += TimeHelper.dateTimeToStr(new Date()) + "|";// 当前时间
          content += SystemInfo.getMaxMemory() + "|";// 最大内存
          content += SystemInfo.getFreeMemory() + "|";// 剩余内存
          content += SystemInfo.getUseMemory() + "|";// 使用内存
          content += SystemInfo.getThreadCount() + "|";// 线程数量
          
          content += ServerConfig.LAST_SEND_TIME + "|";//发送线程最后一次执行时间
          content += String.valueOf(TaskQueue.getSendTaskSize()) + "|";//发送队列大小
          content += String.valueOf(TaskQueue.getReceiveTaskSize()) ;//接收队列大小
          
        }
        else if (req.uri().equalsIgnoreCase("/reset.jsp"))
        {
          log.info("[server][console]服务器收到重启指令。");
          Runtime.getRuntime().exit(0);
        }
        else
        {
          // 发送404错误应答头
          content = "404";
        }
      }

      boolean keepAlive = HttpUtil.isKeepAlive(req);
      // boolean keepAlive = false;

      // 向浏览器写入数据流
      FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(content.getBytes("UTF-8")));

      if ("404".equals(content))
      {
        response.setStatus(HttpResponseStatus.NOT_FOUND);
      }
      else if ("302".equals(content))
      {
        response.setStatus(HttpResponseStatus.FOUND);
        String url = "http://" + req.headers().get("Host") + "/login.jsp";
        response.headers().set(LOCATION, url);
      }

      response.headers().set(CONTENT_TYPE, "text/html; charset=utf-8");
      response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());

      if (!keepAlive)
      {
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
      }
      else
      {
        response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        response.headers().set(KEEP_ALIVE, "timeout=300,max=100");
        ctx.writeAndFlush(response);
      }
    }
    catch (Exception e)
    {
      log.error(e.getMessage(), e);
      throw e;
    }
    finally
    {
      // 对于不再使用的ByteBuf引用计数类型，需要手动释放。
      // http://netty.io/wiki/reference-counted-objects.html
      // ReferenceCountUtil.release(req);
    }
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
  {
    String channelId = ctx.channel().id().asShortText();// 连接ID
    ConsoleChannelMap.remove(channelId);
    log.error(cause.getMessage(), cause);
    ctx.close();
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) throws Exception
  {
    String remoteIpAddress = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
    String channelId = ctx.channel().id().asShortText();// 连接ID

    log.info("[server][console][" + remoteIpAddress + "]控制台连接被断开。");

    ConsoleChannelMap.remove(channelId);
    ctx.close();
  }
}
